package com.jzo2o.foundations.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jzo2o.common.enums.EnableStatusEnum;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.foundations.enums.FoundationStatusEnum;
import com.jzo2o.foundations.mapper.RegionMapper;
import com.jzo2o.foundations.mapper.ServeItemMapper;
import com.jzo2o.foundations.model.domain.Region;
import com.jzo2o.foundations.model.domain.Serve;
import com.jzo2o.foundations.mapper.ServeMapper;
import com.jzo2o.foundations.model.domain.ServeItem;
import com.jzo2o.foundations.model.dto.request.ServePageQueryReqDTO;
import com.jzo2o.foundations.model.dto.request.ServeUpsertReqDTO;
import com.jzo2o.foundations.model.dto.response.ServeResDTO;
import com.jzo2o.foundations.service.IServeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.mysql.utils.PageHelperUtils;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 服务表 服务实现类
 * </p>
 *
 * @author yue
 * @since 2024-12-23
 */
@Service
public class ServeServiceImpl extends ServiceImpl<ServeMapper, Serve> implements IServeService {
    //上面这里继承的父类ServiceImpl,指定了对应的泛型,所以baseMapper就直接是ServeMapper的实例,而且能直接调用mybatisplus提供的基础的sql语句(因为ServeMapper继承了BaseMapper)
        //对于ServiceImpl<ServeMapper, Serve>这个泛型，第一个类型ServeMapper是指定这个服务实现类（ServiceImpl）所关联的 Mapper 类型。
            // 这样做的好处是，在这个服务类中使用baseMapper时，编译器就知道它是ServeMapper类型，可以方便地调用ServeMapper
            // 以及它继承的BaseMapper中的方法来操作数据库中Serve相关的数据。
        //第二个类型Serve是指定这个服务所对应的实体类。这在很多数据库操作场景下很有用，比如在一些自动生成的代码或者基于实体类进行逻辑处理的代码中，知道实体类类型可以更好地完成操作。
    //当然如果不想用默认的baseMapper,可以通过注解注入的方式注入serveMapper,,就相当于是新的实例,基本上直接用baseMapper就好了

    //@Resource
    //private ServeMapper baseMapper;  //下面解释了baseMapper,我就把这个注释掉了
    @Resource
    private ServeItemMapper serveItemMapper;
    @Resource
    private RegionMapper regionMapper;
    
    /**
     * 区域服务分页查询
     * 具体怎么实现,,应该先去看别人的代码,,
     * 老师带领我们看了前面不同人用的分页方法,有两种,第一种直接调用mybatisplus提供的分页方法;第二种用helper实现
     * 老师直接带领我们抄人家写的代码,,直接改参数,,,额,震惊一整年,我还以为都要自己写,自己实现;你要说看,那我肯定能看懂
     * @param servePageQueryReqDTO
     * @return
     */
    @Override
    public PageResult<ServeResDTO> page(ServePageQueryReqDTO servePageQueryReqDTO) {

        //1.下面代码是人家写的,,然后自己抄代码,改参数,实现分页查询;其实自己用helper实现分页查询也会写,,拷贝以前项目
        //return PageHelperUtils.selectPage(serveItemPageQueryReqDTO,
        //        () -> baseMapper.queryList(serveItemPageQueryReqDTO.getServeTypeId(), 
        //                serveItemPageQueryReqDTO.getName(), serveItemPageQueryReqDTO.getActiveStatus()));
        return PageHelperUtils.selectPage(servePageQueryReqDTO,
                () -> baseMapper.queryServeListByRegionId(servePageQueryReqDTO.getRegionId()));
        //2.唯一有点疑惑的,,这个baseMapper能直接调用我定义的方法吗?按理说这个不是在ServeMapper中吗;
        //解释: 调用mapper查询数据，这里由于继承了ServiceImpl<ServeMapper, Serve>，使用baseMapper相当于使用ServeMapper,
            //baseMapper就是ServeMapper的实例,我还以为是BaseMapper的;并且这个baseMapper名称是框架定义好的,不能随便定义一个如monsterMapper它就不能自动关联ServeMapper

    }

    /**
     * 批量添加区域服务
     * @param serveUpsertReqDTOList
     */
    @Override
    @Transactional
    public void batchAdd(List<ServeUpsertReqDTO> serveUpsertReqDTOList) {
        //整体遍历这个传入的List参数
        for (ServeUpsertReqDTO serveUpsertReqDTO : serveUpsertReqDTOList) {
            
        //合法性校验:  添加一定要进行合法校验,来源就是你添加了哪些字段,看是否需要合法校验 
            //比如传进来的参数是不是合法,如果为null,肯定没法查询呀; 非空的合法性校验,在springMVC_yue有一个JSR303也可以进行校验
        //1.检验服务项(serve_item)是否为启用状态,不是启用状态不能新增
            ServeItem serveItem = serveItemMapper.selectById(serveUpsertReqDTO.getServeItemId());
            
            //这里老师用的对象工具类,ObjectUtils.isNull(serveItem)
            if (serveItem == null || serveItem.getActiveStatus() != FoundationStatusEnum.ENABLE.getStatus()) {   
                //抛出异常
                throw new ForbiddenOperationException("服务项不存在或服务项未启用,不允许添加");
            }
            //2.检验是否重复新增,,同一个区域不能有两个相同的服务项,去serve表查询,区域id对应的服务项目id要唯一
                //sql: select * from serve where item_id = ? and region_id = ?
                //因为当前继承了mybatisplus自带的mapper,即serveMapper,所以这里可以直接用lambdaQuery()
            Integer count = lambdaQuery()   //这个lambdaQuery就相当于new LambdaQueryWrapper<ServeItem>(),,
                    .eq(Serve::getServeItemId, serveUpsertReqDTO.getServeItemId())//这个eq,应该是equal的缩写
                    .eq(Serve::getRegionId, serveUpsertReqDTO.getRegionId())
                    .count();
                // 自己理解: 就是用lambda表达式快速查询,拼接上面的sql语句,,只是用来记录查询结果次数的;
                    //即getServeItemId就对应sql语句中的item_id;
                    // serveUpsertReqDTO.getServeItemId()传入的具体值对应上面的 ?
                    //这个`lambdaQuery()`构建的`LambdaQueryWrapper`（是基于 MyBatis - Plus 这样的框架）
                        // 主要用于构建查询条件并执行计数操作,,并不是完全替代了sql语句的查询对象
                //最后,通过.count拿到查询结果的次数
            if (count > 0) {   //说明有查询到结果,即地区id相等,且服务项目id也相同,查询到有结果,,那就不能再新增了
                throw new ForbiddenOperationException(serveItem.getName() + ":" + "服务已存在");
            }
            
            //3.向serve表中插入数据--封装serve对象
            Serve serve = BeanUtils.toBean(serveUpsertReqDTO, Serve.class); //这里是通过转bean的class类型转换的
                //其实我记得以前苍穹外卖老师用的属性拷贝,也是BeanUtils
                // 这个是以前学过的属性拷贝:   BeanUtils.copyProperties(serveUpsertReqDTO, serve);
            //剩下的serve表中的字段,就得单独敲了,,然后我就想到以前用的builder构造器了
                //插入city_code字段
            Long regionId = serve.getRegionId();
            Region region = regionMapper.selectById(regionId);
            String cityCode = region.getCityCode();
            serve.setCityCode(cityCode);
            //不过话说,,剩下的字段都是有默认值,所以不用单独封装插入到表中了;因为上面你查询出来的表格,对应字段就有默认值而不是空
            
            //4.向数据库中的serve表中插入数据
            baseMapper.insert(serve);    
                //因为用的mybatis_plus自带的,所以默认用的实例是baseMapper,因为基础的增删改查方法都封装到BaseMapper.xml中了;不能随便定义成别的名称
                //

        }
    }

    /**
     * 修改区域服务价格
     * @param id    服务id
     * @param price 价格
     * @return
     */
    @Override
    @Transactional
    public Serve update(Long id, BigDecimal price) {
        
        //这里用的lambdaUpdate快捷表达式,
        boolean update = lambdaUpdate()
                .eq(Serve::getId, id)
                .set(Serve::getPrice, price)
                .update();
        if (!update){
            throw new CommonException("修改服务价格失败");   //当你不确定,具体属于哪个异常类型,就都抛CommonException;因为这个是父类异常,异常处理会捕获这个异常
        }
        
        //如果是返回void,普通的一个update语句,上面的lambdaUpdate就已经满足要求了
        //但是老师想要把更新的结果返回,,,因此要执行update语句;  那上面的lambdaUpdate就没啥意义了
        Serve serve = baseMapper.selectById(id);
        return serve;
    }

    /**
     * 区域服务上架
     * @param id  服务id
     * @return
     */
    @Override
    @Transactional
    public Serve onSale(Long id) {
        
        //1. 根据id查询serve表信息
        Serve serve = baseMapper.selectById(id);
        if (serve == null) {
            throw new ForbiddenOperationException("区域服务信息不存在");
        }
        //2.如果serve表中的sale_status是0(草稿)或者1(下家)状态,是可以上架的
        Integer saleStatus = serve.getSaleStatus();
        if (!(saleStatus == FoundationStatusEnum.INIT.getStatus() || saleStatus == FoundationStatusEnum.DISABLE.getStatus())) {
            //除了这两种状态,,都不能上架,,单独做异常处理
            throw new ForbiddenOperationException("只有草稿或下架状态才可上架");
        }
        //3.如果服务项没有启用,也不能上架
        ServeItem serveItem = serveItemMapper.selectById(serve.getServeItemId());
        if(ObjectUtil.isNull(serveItem)){   //这里不为空,还要判断下,自己总忘
            throw new ForbiddenOperationException("所属服务项不存在");
        }
        //服务项目的启用状态
        Integer activeStatus = serveItem.getActiveStatus();
        if (!(activeStatus == FoundationStatusEnum.ENABLE.getStatus() )) {
            //如果服务项目不是启用状态,那这个对应的服务也不能上架
            throw new ForbiddenOperationException("服务项不是启动状态,不可上架");
        }
        //4.更新sale_status
        
        //下面这部分是我自己写的代码,,,虽然简洁,但是面对复杂的业务情况,还是人家的代码逻辑性更好
        //serve.setSaleStatus(FoundationStatusEnum.ENABLE.getStatus());
        //int num = baseMapper.updateById(serve);

        //老师代码
        boolean update = lambdaUpdate()  //前面讲了,在Java基础笔记.md中也有总结
            .eq(Serve::getId, id)
            .set(Serve::getSaleStatus, FoundationStatusEnum.ENABLE.getStatus())
            .update();
        if(!update){
            throw new CommonException("启动服务失败");
        }

        return baseMapper.selectById(id);
    }

    /**
     * 区域服务删除
     * @param id
     */
    @Override
    public void deleteRegionServe(Long id) {
        //1.查询对应状态
        Serve serve = baseMapper.selectById(id);
        if (ObjectUtil.isNull(serve)) {
            throw new ForbiddenOperationException("区域服务不存在");
        }
        //2. 当状态为草稿状态下可以删除,其余状态抛异常
            //感觉这里就不用考虑服务项的状态,,只考虑serve表应该就行
        if(serve.getSaleStatus() != FoundationStatusEnum.INIT.getStatus()){
            throw new ForbiddenOperationException("不是草稿状态,不能删除");
        }
        
        //3.删除对应服务
        baseMapper.deleteById(id);
    }


    /**
     * 区域服务下架
     * @param id         服务id
     * @return
     */
    @Override
    @Transactional
    public Serve offSale(Long id) {
        
        //服务下架后用户将不能对该服务下单。
        
        //1.查询区域服务状态
        Serve serve = baseMapper.selectById(id);
        if (ObjectUtil.isNull(serve)) {
            throw new ForbiddenOperationException("区域服务不存在");
        }
        //2.当区域服务的状态不为上架状态,抛出异常,不能下架
        if (!(serve.getSaleStatus() == FoundationStatusEnum.ENABLE.getStatus())) {
            throw new ForbiddenOperationException("上架状态才能下架");
        }
        //3. 修改为下架状态
        boolean update = lambdaUpdate()  //前面讲了,在Java基础笔记.md中也有总结
                .eq(Serve::getId, id)
                .set(Serve::getSaleStatus, FoundationStatusEnum.DISABLE.getStatus())
                .update();
        if(!update){
            throw new CommonException("启动服务失败");
        }
        
        return baseMapper.selectById(id);
        
    }

    /**
     * 区域服务设置热门 /取消热门
     *  通过传入的不同状态参数,就能复用这个代码
     * @param id
     * @param hotStatus 是否为热门，0：非热门，1：热门
     */
    @Override
    @Transactional
    public void changeHotStatus(Long id, Integer hotStatus) {

        Serve serve = baseMapper.selectById(id);
        //controller层,有@Notnull验证,所以这里以后就不用验证时是否为空了
        //if (ObjectUtil.isNull(serve)) {
        //    throw new ForbiddenOperationException("区域服务不存在");
        //}
        serve.setIsHot(hotStatus);
        serve.setHotTimeStamp(System.currentTimeMillis());    
        //这里的只是一个时间戳,不是具体的时间,并且是Long型,所以没用LocalDateTime.now
            //补充时间戳理解: 时间戳（timestamp）是指格林威治时间 1970 年 1 月 1 日 00:00:00（UTC）起至现在的总秒数（10 位）或总毫秒数（13 位）。
                // 在 Java 中，System.currentTimeMillis()返回的是从这个起始时间点到当前时间的毫秒数。也就是说时间戳就是一个总毫秒数
        
        //修改热门状态
        baseMapper.updateById(serve);
    }

    /**
     * 根据区域id和售卖状态查询关联服务数量
     * @param regionId
     * @param saleStatus  售卖状态，0：草稿，1下架，2上架。可传null，即查询所有状态
     * @return
     */
    @Override
    public int queryServeCountByRegionIdAndSaleStatus(Long regionId, Integer saleStatus) {

        LambdaQueryWrapper<Serve> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Serve::getRegionId, regionId)
                          .eq(ObjectUtil.isNotEmpty(saleStatus), Serve::getSaleStatus, saleStatus);  //这里还可以直接添加验证条件
        Integer count = baseMapper.selectCount(lambdaQueryWrapper);

        //下面是老师的代码,功能完全一样,,不过下面的风格是通过Wrappers直接实例化,Wrappers 是 MyBatis - Plus 提供的一个工具类,,更符合mybatisplue语法
        //LambdaQueryWrapper<Serve> queryWrapper = Wrappers.<Serve>lambdaQuery()
        //        .eq(Serve::getRegionId, regionId)
        //        .eq(ObjectUtil.isNotEmpty(saleStatus), Serve::getSaleStatus, saleStatus);
        //return baseMapper.selectCount(queryWrapper);
        return count;
    }

    /**
     * 根据服务项id和售卖状态查询关联服务数量
     * @param serveItemId
     * @param saleStatus  售卖状态，0：草稿，1下架，2上架。可传null，即查询所有状态
     * @return
     */
    @Override
    public int queryServeCountByServeItemIdAndSaleStatus(Long serveItemId, Integer saleStatus) {

        LambdaQueryWrapper<Serve> queryWrapper = Wrappers.<Serve>lambdaQuery()
                .eq(Serve::getServeItemId, serveItemId)
                .eq(ObjectUtil.isNotEmpty(saleStatus), Serve::getSaleStatus, saleStatus);
        return baseMapper.selectCount(queryWrapper);
    }

}
